package com.mybatis.jpa.plugin;

import com.mybatis.jpa.annotation.MultiTenancyCheck;
import com.mybatis.jpa.annotation.NotMultiTenancyCheck;
import com.mybatis.jpa.context.MultiTenancyContext;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.*;

/**
 * 多租户拦截器
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MultiTenancyPlugin implements Interceptor {

    /**
     * 某个类是否需要进行权限过滤校验
     */
    private Map<String, Boolean> checkResultMap = new HashMap<>();

    private String providerIdField = "provider_id";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (MultiTenancyContext.getProviderId() == null) {
            return invocation.proceed();
        }
        Object target = invocation.getTarget();
        if (target instanceof RoutingStatementHandler) {
            RoutingStatementHandler handler = (RoutingStatementHandler) target;
            StatementHandler delegate = (StatementHandler) getValue(handler, "delegate");
            //通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
            MappedStatement mappedStatement = (MappedStatement) getValue(delegate, "mappedStatement");
            String resource = mappedStatement.getId();
            if (!checkResultMap.containsKey(resource)) {
                String mapperClass = resource.substring(0, resource.lastIndexOf("."));
                try {
                    Class clazz = Class.forName(mapperClass);
                    boolean isMapperHashAnotation = clazz.isAnnotationPresent(MultiTenancyCheck.class);
                    boolean isMethodHashExcludeAnotation = getMethod(clazz, resource.substring(resource.lastIndexOf(".") + 1)).isAnnotationPresent(NotMultiTenancyCheck.class);
                    //mapper 接口注解包含 方法注解不能包含NotMultiTenancyCheck的为true
                    checkResultMap.put(resource, isMapperHashAnotation & (!isMethodHashExcludeAnotation));
                } catch (Exception e) {
                    checkResultMap.put(resource, false);
                }
            }
            if (checkResultMap.get(resource)) {
                String resultSql = hanleSql(delegate.getBoundSql().getSql());
                setValue(delegate.getBoundSql(), "sql", resultSql);
            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }


    /**
     * 处理sql拼接 供应商id条件
     *
     * @param sourceSql 原来的sql
     * @return
     */
    public String hanleSql(String sourceSql) {
        sourceSql = sourceSql.replaceAll("\n", " ").replaceAll("\r\n", " ").replaceAll("\t", " ");
        String upperSql = sourceSql.toUpperCase().trim();
        if (isNotNeedAppend(upperSql)) {
            return sourceSql;
        }
        String preSql = null;
        String afterSql = null;
        if (upperSql.contains(" WHERE ")) {
            preSql = sourceSql.substring(0, upperSql.indexOf(" WHERE ") + 6);
            afterSql = sourceSql.substring(upperSql.indexOf(" WHERE ") + 6);
        } else {
            String whereSql = " WHERE "
                    + providerIdField + "='" + MultiTenancyContext.getProviderId() + "' ";
            if (upperSql.contains(" GROUP BY ")) {
                return handelSql("GROUP BY",upperSql,sourceSql,whereSql);
            }else if (upperSql.contains(" HAVING ")) {
                return handelSql("HAVING",upperSql,sourceSql,whereSql);
            }
            else if (upperSql.contains(" ORDER BY ")) {
                return handelSql("ORDER BY",upperSql,sourceSql,whereSql);
            }
            else if (upperSql.contains("LIMIT")) {
                return handelSql("LIMIT",upperSql,sourceSql,whereSql);
            }
            return sourceSql + whereSql;
        }
        return preSql + " " + providerIdField + "='" + MultiTenancyContext.getProviderId() + "' AND " + afterSql;
    }

    private String handelSql(String tag, String upperSql, String sourceSql, String whereSql) {
        return sourceSql.substring(0, upperSql.indexOf(" " + tag + " ")) + whereSql + sourceSql.substring(upperSql.indexOf(" " + tag + " "));
    }

    /**
     * 是否需要拼接租户id
     *
     * @param sql sql
     * @return true 不需要 false需要
     */
    private boolean isNotNeedAppend(String sql) {
        String tempSql = sql;
        // insert不需要拼接
        if (tempSql.contains("INSERT ")) {
            return true;
        }
        if (tempSql.contains(" WHERE ")) {
            tempSql = tempSql.substring(0, tempSql.indexOf(" WHERE "));
        }
        if (tempSql.contains("SELECT ") || tempSql.contains("DELETE ")) {
            tempSql = tempSql.substring(tempSql.indexOf(" FROM "));
            if (tempSql.contains(" LIMIT ")) {
                tempSql = tempSql.substring(0, tempSql.indexOf(" LIMIT "));
            }
            return tempSql.contains(",") || tempSql.contains(" JOIN ");
        }
        tempSql = tempSql.substring(tempSql.indexOf(" SET "));
        return tempSql.contains(",") || tempSql.contains(" JOIN ");
    }

    /**
     * 反射获取一个值
     *
     * @param obj       obj
     * @param fieldName 字段名称
     * @return obj.字段 的值
     */
    @SuppressWarnings("rawtypes")
    public static Object getValue(Object obj, String fieldName) {
        Field field;
        try {
            List<Field> declaredfields = getAllField(obj.getClass());
            for (int i = 0; i < declaredfields.size(); i++) {
                if (declaredfields.get(i).getName().equals(fieldName)) {
                    field = declaredfields.get(i);
                    field.setAccessible(true);
                    return field.get(obj);
                }
            }
        } catch (SecurityException e) {
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        }
        return null;
    }

    /**
     * 反射设置一个值
     *
     * @param obj       obj
     * @param fieldName 字段名称
     * @param val       被设置的值
     */
    @SuppressWarnings("rawtypes")
    public static void setValue(Object obj, String fieldName, Object val) {
        Field field;
        try {
            List<Field> declaredfields = getAllField(obj.getClass());
            for (int i = 0; i < declaredfields.size(); i++) {
                if (declaredfields.get(i).getName().equals(fieldName)) {
                    field = declaredfields.get(i);
                    field.setAccessible(true);
                    field.set(obj, val);
                    break;
                }
            }
        } catch (SecurityException e) {
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        }
    }

    /**
     * 获取一个class的所有的字段
     *
     * @param clazz class
     * @return 所有字段
     */
    public static List<Field> getAllField(Class clazz) {
        Field[] fields = null;
        List<Field> result = new ArrayList<Field>();
        for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
            try {
                fields = clazz.getDeclaredFields();
                result.addAll(Arrays.asList(fields));
            } catch (Exception e) {
                // 这里甚么都不要做！并且这里的异常必须这样写，不能抛出去。
                // 如果这里的异常打印或者往外抛，则就不会执行clazz = clazz.getSuperclass(),最后就不会进入到父类中了
            }
        }
        return result;
    }

    public static Method getMethod(Class clazz, String methodName) {
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                return method;
            }
            ;
        }
        return null;
    }

    public void setProviderIdField(String providerIdField) {
        this.providerIdField = providerIdField;
    }
}
